package com.example.demo.config;

import com.example.demo.service.IMCosService;
import com.example.demo.support.IMBaseResponse;
import com.example.demo.support.IMMessage;
import com.example.demo.support.UserSig;
import com.example.demo.util.UserSigUtils;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Slf4j
public class IMOperations {

    private RestTemplate restTemplate;
    private String sdkAppId;
    private String secret;
    private final IMCosService cosService;
    private static final String CONSOLE_URL = "https://console.tim.qq.com/%s/%s/%s?sdkappid=%s&identifier=%s&usersig=%s&random=%s&contenttype=%s";
    private static final String VERSION = "v4";
    private static final String CONTENT_TYPE = "json";
    private static final String MANAGER_NAME = "administrator";
    private static UserSig userSig;



    public IMOperations(String sdkAppId, String secret, IMCosService cosService) {
        this.sdkAppId = sdkAppId;
        this.secret = secret;
        this.restTemplate = new RestTemplate();
        this.cosService = cosService;
    }

    public UserSig genUserSig(String user) {
        return UserSigUtils.genUserSig(this.sdkAppId, this.secret, user);
    }

    private String buildUrl(String serviceName, String command, String user) {
        // 优化SIG的使用
        LocalDateTime now = LocalDateTime.now();
        if (userSig == null || userSig.getExpired().isBefore(now)) {
            userSig = this.genUserSig(user);
        }
        return String.format(CONSOLE_URL, VERSION, serviceName, command, this.sdkAppId, user, userSig.getUserSig(), RandomStringUtils.randomNumeric(9), CONTENT_TYPE);
    }

    private HttpEntity<Object> buildRequestEntity(Map<String, Object> data) {
        if (data == null) {
            data = Collections.emptyMap();
        }
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new HttpEntity<>(new Gson().toJson(data), headers);
    }

    /**
     * 核心方法，执行IM命令
     * @param command IM命令
     * @param parameters 命令参数
     * @return 执行结果
     */
    @SuppressWarnings({"rawtypes"})
    public IMBaseResponse executeCommand(IMCommand command, Map<String, Object> parameters) {
        Map result = this.restTemplate.postForObject(this.buildUrl(command.getServiceName(), command.getCommand(), MANAGER_NAME), this.buildRequestEntity(parameters), Map.class);
        IMBaseResponse response = (IMBaseResponse) this.convertMap(command.getResponseType(), null, command.getResponseType(), result);
        if (response.getErrorCode() != 0) {
            response.setErrorInfo(response.transformErrorCode(response.getErrorCode()));
        }
        return response;
    }

    @SuppressWarnings("rawtypes")
    private Object convertMap(Class<?> parent, Object parentInstance, Class<?> cls, Map map) {
        Object instance = BeanUtils.instantiate(cls);
        if (parent == cls) {
            parentInstance = instance;
        }
        Class<?> current = cls;
        while (current != Object.class) {
            this.populate(parent, parentInstance, instance, current.getDeclaredFields(), map);
            current = current.getSuperclass();
        }
        return instance;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void populate(Class<?> parent, Object parentInstance, Object instance, Field[] fields, Map map) {
        for (Field field : fields) {
            Object val = map.get(field.getName());
            if (val == null) {
                continue;
            }
            Object res = val;
            if (val instanceof Map) {
                // 子类
                res = this.convertMap(parent, parentInstance, field.getType(), (Map) val);
                // 处理ErrorCode
                this.transformErrorCode(parent, parentInstance, res);
            } else if (val instanceof List) {
                List list = (List) val;
                if (!list.isEmpty()) {
                    if (list.get(0) instanceof Map) {
                        ParameterizedType type = (ParameterizedType) field.getGenericType();
                        res = list.parallelStream().map(m -> {
                            Object obj = this.convertMap(parent, parentInstance, (Class<?>) type.getActualTypeArguments()[0], (Map) m);
                            // 处理ErrorCode
                            this.transformErrorCode(parent, parentInstance, obj);
                            return obj;
                        }).collect(Collectors.toList());
                    }
                }
            }
            field.setAccessible(true);
            try {
                field.set(instance, res);
            } catch (IllegalAccessException e) {
                log.error("设置字段[{}]出现错误：", field.getName(), e);
            }
        }
    }

    private void transformErrorCode(Class<?> parent, Object parentInstance, Object instance) {
        Method method = ReflectionUtils.findMethod(parent, "transformErrorCode", Integer.class);
        if (method == null) {
            return;
        }
        Method getResultCode = ReflectionUtils.findMethod(instance.getClass(), "getResultCode");
        Method setResultInfo = ReflectionUtils.findMethod(instance.getClass(), "setResultInfo", String.class);
        if (getResultCode == null || setResultInfo == null) {
            return;
        }
        Object res = ReflectionUtils.invokeMethod(getResultCode, instance);
        if (!(res instanceof Integer) || Objects.equals(res, 0)) {
            return;
        }
        ReflectionUtils.invokeMethod(setResultInfo, instance, ReflectionUtils.invokeMethod(method, parentInstance, res));
    }

    @SuppressWarnings("unchecked")
    public List<IMMessage> parseMessages(Map<String, Object> data) {
        List<Map<String, Object>> msgList = (List<Map<String, Object>>) data.getOrDefault("MsgList", Collections.emptyList());
        return msgList.parallelStream().map(msg -> {
            List<Map<String, Object>> msgBody = (List<Map<String, Object>>) msg.getOrDefault("MsgBody", Collections.emptyList());
            return msgBody.parallelStream().map(body -> {
                IMMessage message = new IMMessage();
                message.setFromAccount(msg.get("From_Account").toString());
                message.setToAccount(msg.get("To_Account").toString());
                message.setMsgTimestamp(((Double) msg.get("MsgTimestamp")).intValue());
                message.setMsgSeq(((Double) msg.get("MsgSeq")).intValue());
                message.setMsgRandom(((Double) msg.get("MsgRandom")).intValue());
                String type = body.get("MsgType").toString();
                Map<String, Object> content = (Map<String, Object>) body.getOrDefault("MsgContent", Collections.emptyMap());
                message.setMsgType(type);
                switch (type) {
                    case "TIMTextElem":
                        // 文本消息
                        String text = content.getOrDefault("Text", "").toString();
                        message.setMsgText(text);
                        break;
                    case "TIMCustomElem":
                        // 自定义消息
                        message.setMsgData(content.getOrDefault("Data", "").toString());
                        message.setMsgDesc(content.getOrDefault("Desc", "").toString());
                        message.setMsgExt(content.getOrDefault("Ext", "").toString());
                        break;
                    case "TIMFaceElem":
                        // 表情消息
                        message.setFaceIndex(((Double) content.get("Index")).intValue());
                        message.setFaceData(content.getOrDefault("Data", "").toString());
                        break;
                    case "TIMSoundElem":
                        // 语音消息
                        message.setUrl(this.cosService.upload(content.get("Url").toString(), IMCosService.FileType.AUDIO));
                        message.setSize(((Double) content.get("Size")).intValue());
                        message.setSecond(((Double) content.get("Second")).intValue());
                        break;
                    case "TIMImageElem":
                        // 图片消息
                        List<Map<String, Object>> imageInfo = (List<Map<String, Object>>) content.getOrDefault("ImageInfoArray", Collections.emptyList());
                        imageInfo.forEach(image -> {
                            Map<String, String> urls = Maps.newHashMap();
                            if (Objects.equals(image.get("Type"), 1D)) {
                                // 原图
                                String imageUrl = image.get("URL").toString();
                                if (urls.containsKey(imageUrl)) {
                                    message.setOriginalUrl(urls.get(imageUrl));
                                } else {
                                    message.setOriginalUrl(this.cosService.upload(imageUrl, IMCosService.FileType.IMAGE));
                                    urls.put(imageUrl, message.getOriginalUrl());
                                }
                            }
                            if (Objects.equals(image.get("Type"), 2D)) {
                                // 大图
                                String imageUrl = image.get("URL").toString();
                                if (urls.containsKey(imageUrl)) {
                                    message.setBigUrl(urls.get(imageUrl));
                                } else {
                                    message.setBigUrl(this.cosService.upload(imageUrl, IMCosService.FileType.IMAGE));
                                    urls.put(imageUrl, message.getBigUrl());
                                }
                            }
                            if (Objects.equals(image.get("Type"), 3D)) {
                                // 缩略图
                                String imageUrl = image.get("URL").toString();
                                if (urls.containsKey(imageUrl)) {
                                    message.setThumbUrl(urls.get(imageUrl));
                                } else {
                                    message.setThumbUrl(this.cosService.upload(imageUrl, IMCosService.FileType.IMAGE));
                                    urls.put(imageUrl, message.getThumbUrl());
                                }
                            }
                        });
                        break;
                    case "TIMFileElem":
                        // 文件消息
                        message.setUrl(this.cosService.upload(content.get("Url").toString(), IMCosService.FileType.FILE));
                        message.setSize(((Double) content.get("FileSize")).intValue());
                        message.setFileName(content.get("FileName").toString());
                        break;
                    case "TIMVideoFileElem":
                        // 短视频消息
                        message.setUrl(this.cosService.upload(content.get("VideoUrl").toString(), IMCosService.FileType.VIDEO));
                        message.setThumbUrl(content.get("ThumbUrl").toString());
                        message.setSize(((Double) content.get("VideoSize")).intValue());
                        message.setSecond(((Double) content.get("VideoSecond")).intValue());
                        break;
                }
                return message;
            }).collect(Collectors.toList());
        }).flatMap(List::stream).collect(Collectors.toList());
    }

    public static class DataBuilder {
        private Map<String, Object> data;

        public DataBuilder(Map<String, Object> data) {
            this.data = data;
        }

        public DataBuilder put(String key, Object value) {
            this.data.put(key, value);
            return this;
        }

        public Map<String, Object> buildData() {
            return this.data;
        }
    }

    public static DataBuilder dataBuilder() {
        return new DataBuilder(Maps.newHashMap());
    }
}
